package com.imooc.points.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.imooc.commons.constant.ApiConstant;
import com.imooc.commons.constant.RedisKeyConstant;
import com.imooc.commons.exception.ParameterException;
import com.imooc.commons.model.domain.ResultInfo;
import com.imooc.commons.model.pojo.DinerPoints;
import com.imooc.commons.model.vo.DinerPointsRankVO;
import com.imooc.commons.model.vo.ShortDinerInfo;
import com.imooc.commons.model.vo.SignInDinerInfo;
import com.imooc.commons.utils.AssertUtil;
import com.imooc.points.mapper.DinerPointsMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.*;

/**
 * 积分业务逻辑层
 */
@Service
public class DinerPointsService {

    @Resource
    private DinerPointsMapper dinerPointsMapper;
    @Resource
    private RestTemplate restTemplate;
    @Resource
    private RedisTemplate redisTemplate;
    @Value("${service.name.ms-oauth-server}")
    private String oauthServerName;
    @Value("${service.name.ms-diners-server}")
    private String dinersServerName;
    // 排行榜 TOPN
    private static final int TOPN = 20;

    /**
     * 添加积分
     *
     * @param dinerId 食客ID
     * @param points  积分
     * @param types   类型 0=签到，1=关注好友，2=添加Feed，3=添加商户评论
     */
    @Transactional(rollbackFor = Exception.class)
    public void addPoints(Integer dinerId, Integer points, Integer types) {
        // 基本参数校验
        AssertUtil.isTrue(dinerId == null || dinerId < 1, "食客不能为空");
        AssertUtil.isTrue(points == null || points < 1, "积分不能为空");
        AssertUtil.isTrue(types == null, "请选择对应的积分类型");

        // 插入数据库
        DinerPoints dinerPoints = new DinerPoints();
        dinerPoints.setFkDinerId(dinerId);
        dinerPoints.setPoints(points);
        dinerPoints.setTypes(types);
        dinerPointsMapper.save(dinerPoints);

        // TODO 添加至redis中
        /**
         * member 就是用户的 ID
         * score 就是用户的积分
         * 添加用户积分到 zset 中 自动进行排序
         * 通过 rank 等等直接就能获取到当前排名前几的
         */
        redisTemplate.opsForZSet()
                .incrementScore(RedisKeyConstant.diner_points, dinerId, points);
    }

    /**
     * 查询前 20 积分排行榜，并显示个人排名 -- Redis
     *
     * redis与mysql中相同积分的值排名不一样
     *
     * @param accessToken
     * @return
     */
    public List<DinerPointsRankVO> findDinerPointRankFromRedis(String accessToken) {
        // 获取用户积分信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);

        // 统计积分排行榜
        Set<ZSetOperations.TypedTuple<Integer>> rangeWithScores = redisTemplate.opsForZSet()
                .reverseRangeWithScores(RedisKeyConstant.diner_points.getKey(), 0, 19);

        // 构建一个map key 用户id value 积分信息
        Map<Integer, DinerPointsRankVO> ranksMap = new LinkedHashMap<>();
        // 积分排名Id列表
        List<Integer> rankDinerIds = new ArrayList<>();
        // 初始化排名
        int rank = 1;
        for (ZSetOperations.TypedTuple<Integer> rangeWithScore : rangeWithScores) {
            // 积分
            Double points = rangeWithScore.getScore();
            // 食客Id
            Integer dinerId = rangeWithScore.getValue();
            // 食客Id添加至Id集合
            rankDinerIds.add(dinerId);

            DinerPointsRankVO dinerPointsRankVO = new DinerPointsRankVO();
            dinerPointsRankVO.setTotal(points.intValue());
            dinerPointsRankVO.setId(dinerId);
            dinerPointsRankVO.setRanks(rank);

            // 添加至 map 中
            ranksMap.put(dinerId, dinerPointsRankVO);
            // 排名加1
            rank++;
        }
        // 获取 diner 用户信息
        ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "findByIds?access_token=${access_token}&ids=${}",
                ResultInfo.class, accessToken, rankDinerIds);
        if(resultInfo.getCode() != ApiConstant.SUCCESS_CODE){
            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
        }
        List<LinkedHashMap> dinerInfoMaps = (List<LinkedHashMap>) resultInfo.getData();
        // 完善用户姓名、头像信息
        for (LinkedHashMap dinerInfoMap : dinerInfoMaps) {
            ShortDinerInfo shortDinerInfo = BeanUtil.fillBeanWithMap(dinerInfoMap, new ShortDinerInfo(), false);
            DinerPointsRankVO dinerPointsRankVO = ranksMap.get(shortDinerInfo.getId());
            dinerPointsRankVO.setNickname(shortDinerInfo.getNickname());
            dinerPointsRankVO.setAvatarUrl(shortDinerInfo.getAvatarUrl());
        }
        // 判断个人是否在 ranks 中，如果在增加标识
        if(ranksMap.containsKey(dinerInfo.getId())){
            DinerPointsRankVO dinerPointsRankVO = ranksMap.get(dinerInfo.getId());
            dinerPointsRankVO.setIsMe(1);
            return Lists.newArrayList(ranksMap.values());
        }
        // 如果不在 ranks 中 ，添加标识，放至最后
        // 获取排名
        Long myRank = redisTemplate.opsForZSet().reverseRank(RedisKeyConstant.diner_points.getKey(), dinerInfo.getId());
        if(myRank != null){
            DinerPointsRankVO dinerPointsRankVO = new DinerPointsRankVO();
            BeanUtils.copyProperties(dinerInfo, dinerPointsRankVO);
            dinerPointsRankVO.setIsMe(1);
            dinerPointsRankVO.setRanks(myRank.intValue());
            Double points = redisTemplate.opsForZSet().score(RedisKeyConstant.diner_points.getKey(), dinerInfo.getId());
            dinerPointsRankVO.setTotal(points.intValue());
            ranksMap.put(dinerInfo.getId(), dinerPointsRankVO);
        }

        return Lists.newArrayList(ranksMap.values());
    }

    /**
     * 查询前 20 积分排行榜，并显示个人排名 -- MySQL
     *
     * @param accessToken
     * @return
     */
    public List<DinerPointsRankVO> findDinerPointRank(String accessToken) {
        // 获取登录用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        // 统计积分排行榜
        List<DinerPointsRankVO> ranks = dinerPointsMapper.findTopN(TOPN);
        if(ranks == null || ranks.isEmpty()){
            return Lists.newArrayList();
        }
        // 构建map方面用户查找，不然只能通过循环的方式来获取
        // 构建一个map key 用户id value 积分信息
        Map<Integer, DinerPointsRankVO> ranksMap = new LinkedHashMap<>();
        for(int i = 0; i < ranks.size(); i++) {
            ranksMap.put(ranks.get(i).getId(), ranks.get(i));
        }
        // 判断个人是否在 ranks 中，如果在增加标识
        if(ranksMap.containsKey(dinerInfo.getId())){
            DinerPointsRankVO dinerPointsRankVO = ranksMap.get(dinerInfo.getId());
            dinerPointsRankVO.setIsMe(1);
            return Lists.newArrayList(ranksMap.values());
        }
        // 如果不在 ranks 中 ，添加标识，放至最后
        DinerPointsRankVO myRank = dinerPointsMapper.findDinerRank(dinerInfo.getId());
        myRank.setIsMe(1);
        ranks.add(myRank);
        return ranks;
    }

    /**
     * 获取登录用户信息
     *
     * @param accessToken
     * @return
     */
    private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
        // 必须登录
        AssertUtil.mustLogin(accessToken);
        String url = oauthServerName + "user/me?access_token={accessToken}";
        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
        }
        SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),
                new SignInDinerInfo(), false);
        if (dinerInfo == null) {
            throw new ParameterException(ApiConstant.NO_LOGIN_CODE, ApiConstant.NO_LOGIN_MESSAGE);
        }
        return dinerInfo;
    }

}